﻿using System;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Globalization;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Instance;
using System.Collections;
using System.Xml.Linq;
using WMM.Tapioca.Utilities;

namespace WMM.Tapioca.Instance
{
	/// <summary>
    /// XBRL 2.1 Specification 4.6
    /// An item represents a single fact or business measurement.
	/// </summary>
    public class FactItem : Fact
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private string precisionAttributeValue;

        /// <summary>
        /// The precision attribute this item.
        /// </summary>
        /// <value>
        /// The precision attribute value.
        /// </value>
        public string PrecisionAttributeValue
        {
            get { return precisionAttributeValue; }
            set { precisionAttributeValue = value; }
        }
        
        private string decimalsAttributeValue;

        /// <summary>
        /// The decimal attribute this item.
        /// </summary>
        public string DecimalsAttributeValue
        {
            get { return decimalsAttributeValue; }
            set { decimalsAttributeValue = value; }
        }

        internal bool isRoundedValueCalculated;

		/// <summary>
		/// The context associated with this item.
		/// </summary>
		public Context ContextRef
		{
			get;
			internal set;
		}

		/// <summary>
		/// The name of the context reference associated with this item.
		/// </summary>
		public string ContextRefName
		{
			get;
			private set;
		}

		/// <summary>
		/// The unit associated with this item.
		/// </summary>
		public Unit UnitRef
		{
			get;
			internal set;
		}

		/// <summary>
		/// The name of the unit reference associated with this item.
		/// </summary>
		public string UnitRefName
		{
			get;
			private set;
		}

		/// <summary>
		/// The precision of this item.
		/// </summary>
		public int Precision
		{
			get;
			private set;
		}

		/// <summary>
		/// True if this item has infinite precision. False if this item does not have infinite precision.
		/// </summary>
		public bool IsInfinitePrecision
		{
			get;
			private set;
		}

		/// <summary>
		/// True if this item has inferred precision. False if this item does not have inferred precision.
		/// </summary>
		public bool IsPrecisionInferred
		{
			get;
			private set;
		}

		/// <summary>
		/// The decimals value of this item.
		/// </summary>
		public int Decimals
		{
			get;
			private set;
		}

		/// <summary>
		/// True if this item has infinite decimals. False if this item does not have infinite decimals.
		/// </summary>
		public bool IsInfiniteDecimals
		{
			get;
			private set;
		}

		/// <summary>
		/// True if this item has a specified precision. False if this fact does not have a specified precision.
		/// </summary>
        public bool IsPrecisionSpecified
        {
            get
            {
                if (precisionAttributeValue.Length == 0)
                {
                    return false;
                }

                return true;
            }
        }

		/// <summary>
		/// True if this item has a specified decimals value. False if this fact does not have a specified decimals value.
		/// </summary>
        public bool IsDecimalsSpecified
        {
            get
            {
                if (decimalsAttributeValue.Length == 0)
                {
                    return false;
                }

                return true;
            }
        }

		/// <summary>
		/// The value of this fact.
		/// </summary>
		public string Value
		{
			get;
			private set;
		}

		/// <summary>
		/// True if this fact has a nil value. False if this fact does not have a nil value.
		/// </summary>
		public bool IsNilSpecified
		{
			get;
			private set;
		}

		/// <summary>
		/// The type of this item.
		/// </summary>
		public XmlSchemaType Type
		{
			get;
			private set;
		}

        internal double roundedValue;

        /// <summary>
        /// Returns the rounded value of the fact's actual value. The rounded value is calculated from the precision
        /// (or imferred precision) of the fact's actual value.
        /// </summary>
        public double RoundedValue
        {
            get
            {
                if (!isRoundedValueCalculated)
                {
                    roundedValue = GetRoundedValue();
                    isRoundedValueCalculated = true;
                }

                return roundedValue;
            }
        }

        //------------------------------------------------------------------------------------
        //------------------------------------------------------------------------------------
        internal FactItem(InstanceDocument parentInstance, XElement itemElement, DTS dts)
            : base(parentInstance, itemElement, dts)
        {
            this.ContextRefName = (string)FactElement.Attribute("contextRef") ?? string.Empty;
            this.UnitRefName = (string)FactElement.Attribute("unitRef") ?? string.Empty;
            isRoundedValueCalculated = false;

            this.IsNilSpecified = false;
            XNamespace ns = FactElement.GetNamespaceOfPrefix("xsi") ?? string.Empty;
            string nilValue = (string)FactElement.Attribute(ns + "nil") ?? string.Empty;

            if (nilValue.Equals("true", StringComparison.CurrentCultureIgnoreCase))
            {
                this.IsNilSpecified = true;
            }

            this.Concept = GetConceptFromSchema();
            this.Value = FactElement.Value;

            //if (Concept.SubstitutionGroup == Concept.EnumSubstitutionGroup.Item)
            //{
                this.Type = Concept.Type;
            //}

            if (this.Type.IsNumeric())
            {
                decimalsAttributeValue = (string)FactElement.Attribute("decimals") ?? string.Empty; 

                if (decimalsAttributeValue.Length > 0)
                {
                    if (decimalsAttributeValue.Equals("INF"))
                    {
                        this.IsInfiniteDecimals = true;
                        this.IsInfinitePrecision = true;
                        this.Decimals = GetNumberOfDigitsToRightOfDecimalPoint(this.Value);
                    }
                    else
                    {
                        this.IsInfiniteDecimals = false;
                        this.Decimals = Convert.ToInt32(decimalsAttributeValue);
                    }
                }

                precisionAttributeValue = (string)FactElement.Attribute("precision") ?? string.Empty;

                if (precisionAttributeValue.Length > 0)
                {
                    this.IsPrecisionInferred = false;

                    if (precisionAttributeValue.Equals("INF"))
                    {
                        this.IsInfinitePrecision = true;
                        this.IsInfiniteDecimals = true;
                        InferPrecision();
                    }
                    else
                    {
                        this.IsInfinitePrecision = false;
                        this.Precision = Convert.ToInt32(precisionAttributeValue);
                    }
                }

                if (precisionAttributeValue.Length == 0)
                {
                    InferPrecision();
                }

                if (decimalsAttributeValue.Length == 0 || precisionAttributeValue.Equals("INF"))
                {
                    InferDecimal();
                }
            }
        }


        private void InferPrecision()
        {
            // TODO: For a numeric item of type fractionItemType or type derived by restriction from fractionItemType, a consuming application MUST infer the precision to be equal to 'INF' if it is to be used in calculations.

            this.IsPrecisionInferred = true;
            int calculationPart1Value = GetNumberOfDigitsToLeftOfDecimalPoint();

            if (calculationPart1Value == 0)
            {
                calculationPart1Value = GetNegativeNumberOfLeadingZerosToRightOfDecimalPoint();
            }

            int calculationPart2Value = GetExponentValue();
            int calculationPart3Value = this.Decimals;
            this.Precision = calculationPart1Value + calculationPart2Value + calculationPart3Value;

            if (this.Precision < 0)
            {
                // If the value of the precision attribute of a numeric item is equal to 0 , nothing is known about the 
                // precision of the number, nothing can be inferred about decimals, and thus any consuming v-equals comparison 
                // must be false, and any calculation link summation involving the item must be inconsistent.

                this.Precision = 0;
            }
        }


        private void InferDecimal()
        {
            if (precisionAttributeValue.Equals("INF") || decimalsAttributeValue.Equals("INF"))
            {
                this.Decimals = GetNumberOfDigitsToRightOfDecimalPoint(this.Value);
            }
            else if (this.Value.Equals("0"))
            {
                // For an item of numeric value 0, the inferred decimals is deemed to be INF, treating data values of zero as 
                // a singularity of infinite decimals accuracy (regardless of non-zero value of precision attribute or item syntax,
                // e.g., 0, or 000, or .00).
            }else
            {
                int calculationPart1Value = GetNumberOfDigitsToLeftOfDecimalPoint();
                if (calculationPart1Value == 0)
                    calculationPart1Value = GetNegativeNumberOfLeadingZerosToRightOfDecimalPoint();
                int calculationPart2Value = GetExponentValue();
                this.Decimals = this.Precision - calculationPart1Value + calculationPart2Value;
            }
        }


        private int GetNumberOfDigitsToRightOfDecimalPoint(string value)
        {
            string[] stringsAfterDecimalSplit = value.Split(new char[] { '.' });

            if (stringsAfterDecimalSplit.Length == 2)
            {
                return stringsAfterDecimalSplit[1].Length;
            }
            else
            {
                return 0;
            }
        }


        /// <summary>
        /// Determines whether or not the item type for this fact is a monetary type.
        /// </summary>
        /// <returns>
        /// True if the type for this fact is a monetary type and false otherwise.
        /// </returns>
        internal bool IsMonetary()
        {
            return TypeNameContains("monetary");
        }

        /// <summary>
        /// Determines whether or not the item type for this fact is a pure type.
        /// </summary>
        /// <returns>
        /// True if the type for this fact is a pure type and false otherwise.
        /// </returns>
        internal bool IsPure()
        {
            return TypeNameContains("pure");
        }

        /// <summary>
        /// Determines whether or not the item type for this fact is a shares type.
        /// </summary>
        /// <returns>
        /// True if the type for this fact is a shares type and false otherwise.
        /// </returns>
        internal bool IsShares()
        {
            return TypeNameContains("shares");
        }

        /// <summary>
        /// Determines whether or not the item type for this fact is a decimal type.
        /// </summary>
        /// <returns>
        /// True if the type for this fact is a decimal type and false otherwise.
        /// </returns>
        internal bool IsDecimal()
        {
            return TypeNameContains("decimal");
        }

        /// <summary>
        /// Determines whether or not the fact's item type is of the given type.
        /// </summary>
        /// <returns>
        /// True if the type is of the given type and false otherwise.
        /// </returns>
        private bool TypeNameContains(string typeName)
        {
            try
            {
                return TypeNameContains(typeName, this.Type);
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Determines whether or not the supplied item type is of the given type.
        /// </summary>
        /// <returns>
        /// True if the type is of the given type and false otherwise.
        /// </returns>
        private bool TypeNameContains(string typeName, XmlSchemaType currentType)
        {
            if (currentType.Name.Contains(typeName))
            {
                return true;
            }

            if (currentType is XmlSchemaComplexType)
            {
                XmlSchemaComplexType currentComplexType = currentType as XmlSchemaComplexType;

                if (currentComplexType.DerivedBy == XmlSchemaDerivationMethod.Restriction)
                {
                    return TypeNameContains(typeName, currentComplexType.BaseXmlSchemaType);
                }
            }

            return false;
        }

        //------------------------------------------------------------------------------------
        // Validates a fact.
        //
        // If the fact is associated with a data type, and it should be, then hand the fact
        // off to the data type so that the data type can validate the fact. Some data types
        // have specific requirements for facts that must be checked. For example, monetary
        // types require that their facts have units that are part of the ISO 4217 namespace
        // (http://www.xbrl.org/2003/iso4217). This is checked by the datatype.
        //------------------------------------------------------------------------------------
        internal void Validate()
        {
            ValidateBalance();

            if (IsMonetary())
                ValidateMonetaryType();
            else if (IsPure())
                ValidatePureType();
            else if (IsShares())
                ValidateSharesType();
            else if (IsDecimal())
                ValidateDecimalType();

            //ValidateNumericValue();
        }

        private void ValidateNumericValue()
        {
            if (this.Type.IsNumeric())
            {
                try
                {
                    NormalizeNumericValueElement(FactElement);
                }
                catch (Exception ex)
                {
                    log.Error("Validation error on item", new XbrlException("Item defined as numeric with wrong lexical representation. Item: " 
                              + FactElement.Name + " value: " + FactElement.Value, ex));
                    dts.ValidatedInstance = false;
                    return;
                }
            }

            foreach (XAttribute currAttr in FactElement.Attributes())
            {
                if (currAttr.IsNumeric())
                {
                    try
                    {
                        string currAttrValue = Normalization.NormalizeNumericValue(currAttr);
                    }
                    catch (Exception ex)
                    {
                        log.Error("Validation error on item", new XbrlException("Attribute defined as numeric with wrong lexical representation. Item: "
                                  + currAttr.Name + " value: " + currAttr.Value, ex));
                        dts.ValidatedInstance = false;
                        return;
                    }
                }
            }
        }


        private void NormalizeNumericValueElement(XElement elementToNormalize)
        {
            string currAttrValue = FactElement.Value;

            if (elementToNormalize.GetSchemaInfo().SchemaType != null)
            {
                switch (elementToNormalize.GetSchemaInfo().SchemaType.TypeCode)
                {
                    case XmlTypeCode.Decimal:
                        currAttrValue = Decimal.Parse(elementToNormalize.Value).ToString();
                        break;
                    case XmlTypeCode.Double:
                        currAttrValue = Double.Parse(elementToNormalize.Value).ToString();
                        break;
                    case XmlTypeCode.Float:
                        currAttrValue = float.Parse(elementToNormalize.Value).ToString();
                        break;
                    case XmlTypeCode.Integer:
                        currAttrValue = Int32.Parse(elementToNormalize.Value).ToString();
                        break;
                    case XmlTypeCode.Boolean:
                        if (elementToNormalize.Value.Equals("1"))
                        {
                            currAttrValue = "true";
                        }
                        if (elementToNormalize.Value.Equals("0"))
                        {
                            currAttrValue = "false";
                        }
                        break;
                }
            }
        }

        private void ValidateMonetaryType()
        {
            Unit unitReference = UnitRef;

            if (unitReference == null)
            {
                return;
            }
            if (unitReference.Measures.Count == 0)
            {                
                log.Error("Validation error on item", new XbrlException("Monetary item reported without having a measure."));
                dts.ValidatedInstance = false;
                return;
            }

            string uri = unitReference.Measures[0].Namespace.NamespaceName;

            if (uri == null)
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("WrongMeasureNamespaceForMonetaryFact");
                messageBuilder.AppendFormat(stringFormat, Name, unitReference.Id, "unspecified");
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }

            if ((uri.Length > 0) && !(uri.Equals("http://www.xbrl.org/2003/iso4217")))
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("WrongMeasureNamespaceForMonetaryFact");
                messageBuilder.AppendFormat(stringFormat, Name, unitReference.Id, unitReference.Measures[0].Namespace);
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }

            unitReference.SetCultureAndRegionInfoFromISO4217Code(unitReference.Measures[0].LocalName);

            if ((unitReference.CultureInformation == null) && (unitReference.RegionInformation == null))
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("UnsupportedISO4217CodeForUnitMeasure");
                messageBuilder.AppendFormat(stringFormat, Name, unitReference.Id, unitReference.Measures[0].LocalName);
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }
        }


        

        /// <summary>
        /// Section 5.1.1.2 of the XBRL 2.1 Specification
        /// An optional balance attribute MAY be added to the definition of an element if its type is monetaryItemType 
        /// or derived from monetaryItemType. The balance attribute MUST NOT be used on items that do not have type equal
        /// to the monetaryItemType or to a type that is derived from monetaryItemType.
        /// </summary>
        
        private void ValidateBalance()
        {
            foreach (XmlAttribute attr in this.Concept.SchemaElement.UnhandledAttributes)
            {
                if (attr.LocalName.Equals("balance"))
                {
                    if (!this.Concept.SchemaElement.SchemaTypeName.Name.Equals("monetaryItemType"))
                    {
                        log.Error("Validation error on item", new XbrlException("Any element with a balance attibute MUST have a type attribute value of monetaryItemType."));
                        dts.ValidatedInstance = false;
                    }
                }
            }
        }


        /// <summary>
        /// Validate pure item types.
        /// </summary>
        private void ValidatePureType()
        {
            string unitMeasureLocalName = string.Empty;
            Unit unitReference = UnitRef;
            bool isPureMeasureFound = true;

            if (unitReference.Measures.Count != 1)
            {
                isPureMeasureFound = false;
            }

            if (isPureMeasureFound)
            {
                unitMeasureLocalName = unitReference.Measures[0].LocalName;
                isPureMeasureFound = unitMeasureLocalName.Equals("pure");
            }

            if (!isPureMeasureFound)
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("PureItemTypeUnitLocalNameNotPure");
                messageBuilder.AppendFormat(stringFormat, Name, unitReference.Id, unitMeasureLocalName);
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }
        }


        /// <summary>
        /// Validate shares item types.
        /// </summary>
        private void ValidateSharesType()
        {
            bool isSharesMeasureFound = true;
            string unitMeasureLocalName = string.Empty;
            Unit unitReference = UnitRef;

            if (unitReference.Measures.Count != 1)
            {
                isSharesMeasureFound = false;
            }

            if (isSharesMeasureFound)
            {
                unitMeasureLocalName = unitReference.Measures[0].LocalName;
                isSharesMeasureFound = unitMeasureLocalName.Equals("shares");
            }

            if (!isSharesMeasureFound)
            {
                StringBuilder messageBuilder = new StringBuilder();
                string stringFormat = AssemblyResources.GetName("SharesItemTypeUnitLocalNameNotShares");
                messageBuilder.AppendFormat(stringFormat, Name, unitReference.Id, unitMeasureLocalName);
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }
        }


        /// <summary>
        /// Validate decimal item types.
        /// </summary>
        private void ValidateDecimalType()
        {
            if (IsNilSpecified)
            {
                ValidateNilDecimalType();
            }
            else
            {
                ValidateNonNilDecimalType();
            }
        }


        private void ValidateNonNilDecimalType()
        {
            if (!IsPrecisionSpecified && !IsDecimalsSpecified)
            {
                string messageFormat = AssemblyResources.GetName("NumericFactWithoutSpecifiedPrecisionOrDecimals");
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.AppendFormat(messageFormat, Name, Id);
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }

            if (IsPrecisionSpecified && IsDecimalsSpecified)
            {
                string messageFormat = AssemblyResources.GetName("NumericFactWithSpecifiedPrecisionAndDecimals");
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.AppendFormat(messageFormat, Name, Id);
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }
        }


        private void ValidateNilDecimalType()
        {
            if ((IsPrecisionSpecified) || (IsDecimalsSpecified))
            {
                string messageFormat = AssemblyResources.GetName("NilNumericFactWithSpecifiedPrecisionOrDecimals");
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.AppendFormat(messageFormat, Name, Id);
                log.Error("Validation error on item", new XbrlException(messageBuilder.ToString()));
                dts.ValidatedInstance = false;
            }
        }

        //------------------------------------------------------------------------------------
        // Returns true if this Fact is Context Equal (c-equal) to a supplied fact, and false
        // otherwise. See section 4.10 of the XBRL 2.1 spec for more information.
        //------------------------------------------------------------------------------------
        internal bool ContextEquals(FactItem otherFact)
        {
            if (Object.ReferenceEquals(this.ContextRef, otherFact.ContextRef))
            {
                return true;
            }

            return this.ContextRef.StructureEquals(otherFact.ContextRef);
        }

        //------------------------------------------------------------------------------------
        // Returns true if this Fact is Parent Equal (p-equal) to a supplied fact, and false
        // otherwise. See section 4.10 of the XBRL 2.1 spec for more information.
        //------------------------------------------------------------------------------------
        internal bool ParentEquals(FactItem otherFact)
        {
            if (FactElement == null)
            {
                return false;
            }

            return FactElement.Parent.Equals(otherFact.FactElement.Parent);
        }

        //------------------------------------------------------------------------------------
        // Returns true if this Fact is Unit Equal (u-equal) to a supplied fact, and false
        // otherwise. See section 4.10 of the XBRL 2.1 spec for more information.
        //------------------------------------------------------------------------------------
        internal bool UnitEquals(FactItem otherFact)
        {
            // One numeric item and one non-numeric item

            if ((this.UnitRef != null && otherFact.UnitRef == null) ||
                (this.UnitRef == null && otherFact.UnitRef != null))
            {
                return false;
            }

            // Any pair of non-numeric items

            if (this.UnitRef == null && otherFact.UnitRef == null)
            {
                return true;
            }

            return this.UnitRef.StructureEquals(otherFact.UnitRef);
        }

        /// <summary>
        /// Calculates the number of digits to the left of the decimal point. Leading zeros are not counted.
        /// </summary>
        /// <returns>
        /// The number of digits to the left of the decimal point. Leading zeros are not counted.
        /// </returns>
        private int GetNumberOfDigitsToLeftOfDecimalPoint()
        {
            if (this.Value == null)
            {
                return 0;
            }

            string[] parsedValue = ParseValueIntoComponentParts();
            string withoutLeadingZeros = parsedValue[0].TrimStart(new char[] { '0' });
            return withoutLeadingZeros.Length;
        }

        /// <summary>
        /// Calculates the negative number of leading zeros to the right of the decimal point.
        /// </summary>
        /// <returns>
        /// The negative number of leading zeros to the right of the decimal point.
        /// </returns>
        private int GetNegativeNumberOfLeadingZerosToRightOfDecimalPoint()
        {
            if (this.Value == null)
            {
                return 0;
            }

            string[] parsedValue = ParseValueIntoComponentParts();
            string valueToTheRightOfTheDecimal = parsedValue[1];

            if (string.IsNullOrEmpty(valueToTheRightOfTheDecimal))
            {
                return 0;
            }

            int numberOfLeadingZeros = 0;
            int index = 0;

            while (index < valueToTheRightOfTheDecimal.Length)
            {
                if (valueToTheRightOfTheDecimal[index] == '0')
                {
                    numberOfLeadingZeros++;
                    index++;
                }
                else
                {
                    index = valueToTheRightOfTheDecimal.Length;
                    break;
                }
            }

            return -numberOfLeadingZeros;
        }

        /// <summary>
        /// Calculates the value of the exponent in the lexical representation of the fact value.
        /// </summary>
        /// <returns>
        /// The value of the exponent in the lexical representation of the fact value.
        /// </returns>
        private int GetExponentValue()
        {
            if (this.Value == null)
            {
                return 0;
            }

            string[] parsedValue = ParseValueIntoComponentParts();

            if (string.IsNullOrEmpty(parsedValue[2]))
            {
                return 0;
            }

            int exponentValue;
            bool success = int.TryParse(parsedValue[2], out exponentValue);

            if (success)
            {
                return exponentValue;
            }

            return 0;
        }

        /// <summary>
        /// <para>
        /// Parses the fact value into three main parts:
        /// </para>
        /// <list>
        /// <item>
        /// Values to the left of the decimal point
        /// </item>
        /// <item>
        /// Values to the right of the decimal point
        /// </item>
        /// <item>
        /// Exponent value
        /// </item>
        /// </list>
        /// </summary>
        /// <para>
        /// Some of these values may be empty if the original value did not carry all of these components.
        /// </para>
        /// <returns>A string array of length 3. Item 0 contains the value before the decimal point,
        /// item 1 contains the value after the decimal point, and item 2 contains the exponent value. If any
        /// of these elements are not available in the original value, then their individual value within the
        /// array will be empty.</returns>
        private string[] ParseValueIntoComponentParts()
        {
            return ParseValueIntoComponentParts(this.Value);
        }

        /// <summary>
        /// <para>
        /// Parses a string representation of a fact value into three main parts:
        /// </para>
        /// <list>
        /// <item>
        /// Values to the left of the decimal point
        /// </item>
        /// <item>
        /// Values to the right of the decimal point
        /// </item>
        /// <item>
        /// Exponent value
        /// </item>
        /// </list>
        /// </summary>
        /// <para>
        /// Some of these values may be empty if the original value did not carry all of these components.
        /// </para>
        /// <returns>A string array of length 3. Item 0 contains the value before the decimal point,
        /// item 1 contains the value after the decimal point, and item 2 contains the exponent value. If any
        /// of these elements are not available in the original value, then their individual value within the
        /// array will be empty.</returns>
        private string[] ParseValueIntoComponentParts(string originalValue)
        {
            string[] arrayToReturn = new string[3];

            string[] stringsAfterExponentSplit = originalValue.Split(new char[] { 'e', 'E' });

            if (stringsAfterExponentSplit.Length == 2)
            {
                arrayToReturn[2] = stringsAfterExponentSplit[1];
            }

            string numericValue = stringsAfterExponentSplit[0];
            string[] stringsAfterDecimalSplit = numericValue.Split(new char[] { '.' });
            arrayToReturn[0] = stringsAfterDecimalSplit[0].Replace("-","");

            if (stringsAfterDecimalSplit.Length == 2)
            {
                arrayToReturn[1] = stringsAfterDecimalSplit[1];
            }

            return arrayToReturn;
        }

        private double GetRoundedValue()
        {
            double roundedValue = Convert.ToDouble(this.Value);
            //double RoundedValue = Convert.ToDouble(this.Value, CultureInfo.InvariantCulture);
            return Round(roundedValue);
        }

        /// <summary>
        ///  Round a given value using the decimal available in the fact or inferred.
        /// </summary>
        /// <param name="originalValue"></param>
        /// <returns></returns>
        public double Round(double originalValue)
        {
            double roundedValue = originalValue;

            if (this.IsInfiniteDecimals)
            {
                return roundedValue;
            }

            if (this.Decimals >= 0)
            {
                roundedValue = Math.Round(roundedValue, this.Decimals);
            }
            else
            {
                    double powerOfTen = Math.Pow(10.0, (double)(Math.Abs(this.Decimals)));
                    roundedValue = roundedValue / powerOfTen;
                    roundedValue = Math.Round(roundedValue);
                    roundedValue = roundedValue * powerOfTen;
            }

            return roundedValue;
        }

    }
}
